结构型-装饰模式

概念

Decorator Pattern:不改变原有对象的前提下,动态地给一个对象增加一些额外的功能。

img

运用了AOP设计思想,用途广泛,比如logger,防抖…给系统,函数扩展新功能,先拦截再处理。

使用

现在Es7的一个提案(类似 java 注解的语法糖):Decorator,可以实现 装饰者模式

Babel转码器已经支持。因为很多浏览器还不支持,在项目中,需要babel转化成Es5。

下面是vue项目中的使用:

  • npm安装依赖

    1
    npm install babel-core babel-loader babel-plugin-transform-decorators babel-plugin-transform-decorators-legacy babel-preset-env
  • 配置.babelrc文件

    1
    2
    3
    4
    {
    "presets": ["env"],
    "plugins": ["transform-decorators-legacy"]
    }
  • webpack.config.js中添加babel-loader

    1
    2
    3
    4
    5
    module: {
    rules: [
    { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
    ],
    }
  • 如果为VsCode,可能还需要在项目根目录下添加以下tsconfig.json文件来组织一个ts检查的报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "compilerOptions": {
    "experimentalDecorators": true,
    "allowJs": true,
    "lib": [
    "es6"
    ],
    }
    }

举例

借鉴淘宝前端团队写的文章:ES7 Decorator写了一下例子

下面实现,为钢铁侠装备盔甲(纯粹的装饰模式,不增加对原有类的接口)和增加飞行能力(半透明的装饰模式,有点像适配器模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 装备盔甲
function decorateArmour(target, key, descriptor) {
// taget 装饰的对象,这里是 Man类
// key 被装饰的具体方法,这里是 init
// descriptor 方法特性值的描述对象,这里返回的是
/*{
configurable: true
enumerable: false
value: ƒ init(def, atk, hp)
writable: true
__proto__: Object
}
*/
const method = descriptor.value;
let moreDef = 100;
let ret;
descriptor.value = (...args) => {
args[0] += moreDef;
ret = method.apply(target,args);
return ret;
}
return descriptor;
}

// 增加飞行能力
function addFly(canFly) {
return function(target) {
// target 装饰的对象,这里是 Man类
target.canFly = canFly;
let extra = canFly ? '(增加飞行能力)' : '';
let method = target.prototype.toString;
target.prototype.toString = (...args) => {
return method.apply(target.prototype, args) + extra;
}
return target;
}
}

@addFly(true)
class Man {
constructor(def = 2, atk = 3, hp = 3) {
this.init(def, atk, hp);
}

@decorateArmour
init(def, atk, hp) {
this.def = def; // 防御值
this.atk = atk; // 攻击力
this.hp = hp; // 血量
}
toString() {
return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
}
}

var tony = new Man();
console.log(`tony:当前状态 ===> `, tony.toString());

优点

  • 比继承灵活:继承是在编译器起作用,装饰者模式可以在运行时扩展一个对象的功能;另外也可以通过配置文件在运行时选择不同的装饰器,从而实现不同的行为;也通过不同的组合,实现不同的效果。
  • 符合开闭原则:不用更改原有对象,只需要添加新的在装饰类,就可以达到增强的效果

缺点

  • 需要创建一些具体装饰类,会增加系统的复杂度